package alibaba.client.service;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import alibaba.client.utils.Utils;
import alibaba.common.CheckEntry;
import alibaba.common.utils.Base64;
import alibaba.common.utils.CommonUtils;
import alibaba.common.utils.SignatureUtils;

public class DefaultClientBackupService implements ClientBackupService {
    
    private String hostname;
    private int port;
    
    public DefaultClientBackupService(String hostname, int port) {
        this.hostname = hostname;
        this.port = port;
    }
    
    private String createSnapshotQuery(int snapshotNumber) {
        return snapshotNumber == -1 ? null : "snapshot="+snapshotNumber;
    }

    
    private URL generateURL(String path, String query) throws MalformedURLException {
        try {
            URI uri = new URI("http", null, hostname, port, path, query, null);
            return uri.toURL();
        } catch (URISyntaxException e) {
            throw new MalformedURLException(path);
        }
    }
    
    private HttpURLConnection prepareHttpURLConnection(URL url, String userName) throws IOException {
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.addRequestProperty("Authorization", "Alibaba " + userName);
        return connection;
    }
    
    private ClientBackupServiceResponse criticalFailureResponse(String message) {
        ClientBackupServiceResponse response = new ClientBackupServiceResponse();
        response.setCritical();
        response.setMessage(message);
        return response;
    }
    
    private ClientBackupServiceResponse failureResponse(String message) {
        ClientBackupServiceResponse response = new ClientBackupServiceResponse();
        response.setFailure();
        response.setMessage(message);
        return response;
    }
    
    private ClientBackupServiceResponse successResponse(String message) {
        ClientBackupServiceResponse response = new ClientBackupServiceResponse();
        response.setMessage(message);
        return response;
    }
    
    private ClientBackupServiceResponse successResponse() {
        return successResponse(null);
    }
    
    @Override
    public ClientBackupServiceResponse backupFile(String fileName, String userName) {
        
        File file = new File(fileName);
        if (!file.isFile()) {
            return criticalFailureResponse(fileName + " not found");
        }
        if (!file.canRead()) {
            return criticalFailureResponse(fileName + " is not accessible");
        }
        
        String linuxFileName = Utils.convertToUnixPath(fileName);
        linuxFileName = CommonUtils.makeNotEndWithUnixSeparator(linuxFileName);
        
        try {
            byte[] localSignature = SignatureUtils.sign(file.getAbsolutePath());
            ClientBackupServiceResponse backupSignatureResponse = backupFileSignature(linuxFileName, localSignature, userName);
            
            if (backupSignatureResponse.isSuccess() || backupSignatureResponse.isCriticalFailure()) {
                return backupSignatureResponse;
            } else {
                return backupFileData(file, fileName, linuxFileName, userName);
            }
            
        } catch (IOException e) {
            return criticalFailureResponse("Unexpected error occured");
        } 
    }

    private ClientBackupServiceResponse backupFileSignature(String linuxFileName, byte[] signature, String userName) {
        
        try {

            String base64Signature = Base64.encodeBytes(signature, Base64.URL_SAFE);
            
            URL url = generateURL(linuxFileName, "signature="+base64Signature);
            HttpURLConnection connection = prepareHttpURLConnection(url, userName);
            connection.setRequestMethod("PUT");
            
            connection.connect();
            
            int rc = connection.getResponseCode();
            if (rc == HttpURLConnection.HTTP_OK) {
                return successResponse();
            } else if (rc == HttpURLConnection.HTTP_NOT_FOUND) {
                return failureResponse("Signature not found on server");
            } else {
                return criticalFailureResponse("Problem connecting to server");
            }
            
        } catch (ProtocolException e) {
            return criticalFailureResponse("Unexpected error occured");
        } catch (MalformedURLException e) {
            return criticalFailureResponse("Problem with provided server URI");
        } catch (IOException e) {
            return criticalFailureResponse("Unexpected error occured");
        } 
    }
    
    private ClientBackupServiceResponse backupFileData(File file, String fileName, String linuxFileName, String userName) {
        
        FileInputStream inputStream = null;
        
        try {

            URL url = generateURL(linuxFileName, null);
            HttpURLConnection connection = prepareHttpURLConnection(url, userName);
            
            connection.setDoOutput(true);
            connection.setRequestMethod("PUT");
            connection.connect();
            
            inputStream = new FileInputStream(file);
            CommonUtils.readInputStreamIntoOutputStream(inputStream, connection.getOutputStream());

            int rc = connection.getResponseCode();
            if (rc == HttpURLConnection.HTTP_OK) {
                return successResponse();
            } else {
                return criticalFailureResponse("Problem connecting to server");
            }
            
        } catch (ProtocolException e) {
            return criticalFailureResponse("Unexpected error occured");
        } catch (FileNotFoundException e) {
            return criticalFailureResponse(fileName + " not found");
        } catch (MalformedURLException e) {
            return criticalFailureResponse("Problem with provided server URI");
        } catch (IOException e) {
            return criticalFailureResponse("Unexpected error occured");
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // not much to do about this
                }
            }
        }
    }
    
    @Override
    public ClientBackupServiceResponse restoreFile(String fileName, String userName, int snapshotNumber) {

        File file = new File(fileName);
        
        File parentFile = file.getParentFile();
        if (parentFile != null && !parentFile.exists()) {
            parentFile.mkdirs();
        }
        
        String linuxFileName = Utils.convertToUnixPath(fileName);
        linuxFileName = CommonUtils.makeNotEndWithUnixSeparator(linuxFileName);
        
        FileOutputStream outputStream = null;
        
        try {
            
            String snapshot = createSnapshotQuery(snapshotNumber);
            
            URL url = generateURL(linuxFileName, snapshot);
            HttpURLConnection connection = prepareHttpURLConnection(url, userName);
            
            connection.setRequestMethod("GET");
            connection.connect();
            
            int rc = connection.getResponseCode();
            if (rc == HttpURLConnection.HTTP_OK) {
                outputStream = new FileOutputStream(file);
                CommonUtils.readInputStreamIntoOutputStream(connection.getInputStream(), outputStream);
            } else if (rc == HttpURLConnection.HTTP_NOT_FOUND) {
                return failureResponse("File not found on remote server");
            } else {
                return criticalFailureResponse("Problem connecting to server");
            }
        } catch (MalformedURLException e) {
            return criticalFailureResponse("Problem with provided server URI");
        } catch (ProtocolException e) {
            return criticalFailureResponse("Unexpected error occured");
        } catch (IOException e) {
            return criticalFailureResponse("Unexpected error occured");
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    // not much to do about this
                }
            }
        }
        return successResponse();
    }

    @Override
    public ClientBackupServiceResponse backupDir(String dirName, String userName) {

        File dir = new File(dirName);
        if (!dir.isDirectory()) {
            return failureResponse(dirName + " not found");
        }
        
        for (File file : dir.listFiles()) {
            if (file.isFile()) {
                ClientBackupServiceResponse status = backupFile(file.getAbsolutePath(), userName);
                if (!status.isSuccess()) {
                    return status;
                }
            }
        }
        
        return successResponse();
    }

    @Override
    public ClientBackupServiceResponse restoreDir(String dirName, String userName, int snapshotNumber) {

        ClientBackupServiceResponse listDirResponse = listDir(dirName, userName, snapshotNumber);
        if (!listDirResponse.isSuccess()) {
            return listDirResponse;
        }
        
        @SuppressWarnings("unchecked")
        List<String> remoteFiles = (List<String>)listDirResponse.getResponseBody();
        
        for (String file : remoteFiles) {
            ClientBackupServiceResponse status = restoreFile(file, userName, snapshotNumber);
            if (!status.isSuccess()) {
                return status;
            }
        }
        
        return successResponse();
    }

    @Override
    public ClientBackupServiceResponse listDir(String dirName, String userName, int snapshotNumber) {

        String linuxDirName = Utils.convertToUnixPath(dirName);
        linuxDirName = CommonUtils.makeEndWithUnixSeparator(linuxDirName);
        
        try {
            
            String snapshot = createSnapshotQuery(snapshotNumber);
            
            URL url = generateURL(linuxDirName, snapshot);
            HttpURLConnection connection = prepareHttpURLConnection(url, userName);
            
            connection.setRequestMethod("GET");
            connection.connect();
            
            int rc = connection.getResponseCode();
            if (rc == HttpURLConnection.HTTP_OK) {
                List<String> responseBody = new ArrayList<String>();
                BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String line = null;
                while ((line = reader.readLine()) != null) {
                    responseBody.add(line);
                }
                ClientBackupServiceResponse response = successResponse();
                response.setResponseBody(responseBody);
                return response;
            } else if (rc == HttpURLConnection.HTTP_NOT_FOUND) {
                return failureResponse("Directory not found on remote server");
            } else {
                return criticalFailureResponse("Problem connecting to server");
            }
        } catch (MalformedURLException e) {
            return criticalFailureResponse("Problem with provided server URI");
        } catch (ProtocolException e) {
            return criticalFailureResponse("Unexpected error occured");
        } catch (IOException e) {
            return criticalFailureResponse("Unexpected error occured");
        } 
    }

    @Override
    public ClientBackupServiceResponse checkDir(String dirName, String userName) {
        
        String linuxDirName = Utils.convertToUnixPath(dirName);
        linuxDirName = CommonUtils.makeEndWithUnixSeparator(linuxDirName);
        
        try {
            URL url = generateURL(linuxDirName, "check");
            HttpURLConnection connection = prepareHttpURLConnection(url, userName);
            
            connection.setRequestMethod("GET");
            connection.connect();
            
            int rc = connection.getResponseCode();
            if (rc == HttpURLConnection.HTTP_OK) {
                
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                CommonUtils.readInputStreamIntoOutputStream(connection.getInputStream(), outputStream);
                
                List<CheckEntry> responseBody = new ArrayList<CheckEntry>();
                
                String rawResponseBody = new String(outputStream.toByteArray());
                BufferedReader reader = new BufferedReader(new StringReader(rawResponseBody));
                String line = null;
                while ((line = reader.readLine()) != null) {
                    String[] split = line.split(",");
                    if (split.length == 3) {
                        String fileName = split[0].trim();
                        String[] rawSnapshots = split[1].trim().split(" ");
                        if (rawSnapshots.length < 1) {
                            return criticalFailureResponse("Unexpected error occured"); 
                        }
                        int[] snapshots = new int[rawSnapshots.length];
                        for (int i=0; i<rawSnapshots.length; i++) {
                            try {
                                snapshots[i] = Integer.parseInt(rawSnapshots[i].trim());
                            } catch (NumberFormatException e) {
                                return criticalFailureResponse("Unexpected error occured");
                            }
                        }
                        String base64Signature = split[2].trim();
                        byte[] signature = Base64.decode(base64Signature, Base64.URL_SAFE);
                        
                        CheckEntry entry = new CheckEntry(fileName, snapshots, signature);
                        responseBody.add(entry);
                    } else {
                        return criticalFailureResponse("Unexpected error occured");
                    }
                }
                
                ClientBackupServiceResponse response = successResponse();
                response.setResponseBody(responseBody);
                return response;
                
            } else if (rc == HttpURLConnection.HTTP_NOT_FOUND) {
                return failureResponse("Directory not found on server");
            } else {
                return criticalFailureResponse("Problem connecting to server");
            }
        } catch (MalformedURLException e) {
            return criticalFailureResponse("Problem with provided server URI");
        } catch (ProtocolException e) {
            return criticalFailureResponse("Unexpected error occured");
        } catch (IOException e) {
            return criticalFailureResponse("Unexpected error occured");
        } 
    }

    @SuppressWarnings("unused")
    private ClientBackupServiceResponse getSignature(String fileName, String userName) {
        
        String linuxFileName = Utils.convertToUnixPath(fileName);
        linuxFileName = CommonUtils.makeNotEndWithUnixSeparator(linuxFileName);
        
        try {
            URL url = generateURL(linuxFileName, "signature");
            HttpURLConnection connection = prepareHttpURLConnection(url, userName);
            
            connection.setRequestMethod("GET");
            connection.connect();
            
            int rc = connection.getResponseCode();
            if (rc == HttpURLConnection.HTTP_OK) {
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                CommonUtils.readInputStreamIntoOutputStream(connection.getInputStream(), outputStream);
                String signatureBase64 = new String(outputStream.toByteArray());
                byte[] signature = Base64.decode(signatureBase64, Base64.URL_SAFE);
                ClientBackupServiceResponse response = successResponse();
                response.setResponseBody(signature);
                return response;
            } else if (rc == HttpURLConnection.HTTP_NOT_FOUND) {
                return failureResponse("File not found on server");
            } else {
                return criticalFailureResponse("Problem connecting to server");
            }
        } catch (MalformedURLException e) {
            return criticalFailureResponse("Problem with provided server URI");
        } catch (ProtocolException e) {
            return criticalFailureResponse("Unexpected error occured");
        } catch (IOException e) {
            return criticalFailureResponse("Unexpected error occured");
        } 
    }

    @Override
    public ClientBackupServiceResponse createSnapshot(String userName, int snapshotNumber) {
        return doSnapshot(userName, snapshotNumber, true); 
    }

    @Override
    public ClientBackupServiceResponse deleteSnapshot(String userName, int snapshotNumber) {
        return doSnapshot(userName, snapshotNumber, false);
    }
    
    /**
     * 
     * @param username
     * @param snapshotNumber
     * @param isCreate if true send a create request, else send a delete request
     * @return
     */
    private ClientBackupServiceResponse doSnapshot(String userName, int snapshotNumber, boolean isCreate) {
        try {
            URL url = generateURL(String.valueOf(CommonUtils.UNIX_SEPARATOR), String.valueOf(snapshotNumber));
            HttpURLConnection connection = prepareHttpURLConnection(url, userName);
            
            if (isCreate) {
                connection.setRequestMethod("PUT");
            } else {
                connection.setRequestMethod("DELETE");
            }
            
            connection.connect();
            
            int rc = connection.getResponseCode();
            if (rc == HttpURLConnection.HTTP_OK) {
                return successResponse();
            } else if (rc == HttpURLConnection.HTTP_INTERNAL_ERROR) {
                return criticalFailureResponse("Unexpected error occured");
            } else if (rc == HttpURLConnection.HTTP_BAD_REQUEST) {
                return criticalFailureResponse("Illegal request");
            } else {
                return criticalFailureResponse("Problem connecting to server");
            }
        } catch (MalformedURLException e) {
            return criticalFailureResponse("Problem with provided server URI");
        } catch (ProtocolException e) {
            return criticalFailureResponse("Unexpected error occured");
        } catch (IOException e) {
            return criticalFailureResponse("Unexpected error occured");
        } 
    }
}
